#include "parafluidsph.h"
#include "parafluidsphdensity.h"
#include "parafluidsphforce.h"
#include "parafluidsphposition.h"

#include <QSemaphore>
#include <QThread>
#include <QVector>

using namespace ParaFluid;

SPH::~SPH()
{
	/* does nothing */
}

SPH::SPH(int triangleBuckets, int atomBuckets, float smoothingDistance, float timeStep, const Vector & gravity)
	: atomVoxelizer(atomBuckets, smoothingDistance)
	, triangleVoxelizer(triangleBuckets, smoothingDistance)
	, _currentTimeStep(timeStep)
	, _currentGravity(gravity)
	, _newSmoothingDistance(smoothingDistance)
	, _newTimeStep(timeStep)
	, _newGravity(gravity)
	, _lastTimeStep(timeStep)
	, _clear(false)
{
	/* does nothing */
}

void SPH::serialize(QDataStream & stream) const
{
	stream << atomVoxelizer;
	stream << _currentTimeStep;
	stream << _currentGravity;
	stream << _newSmoothingDistance;
	stream << _newTimeStep;
	stream << _newGravity;
	stream << _lastTimeStep;
	stream << _clear;
}

void SPH::unserialize(QDataStream & stream)
{
	stream >> atomVoxelizer;
	stream >> _currentTimeStep;
	stream >> _currentGravity;
	stream >> _newSmoothingDistance;
	stream >> _newTimeStep;
	stream >> _newGravity;
	stream >> _lastTimeStep;
	stream >> _clear;
}

float SPH::smoothingDistance() const
{
	return _newSmoothingDistance;
}

float SPH::timeStep() const
{
	return _newTimeStep;
}

float SPH::lastTimeStep() const
{
	return _lastTimeStep;
}

const Vector & SPH::gravity() const
{
	return _newGravity;
}

void SPH::setSmoothingDistance(float value)
{
	_newSmoothingDistance = value;
}

void SPH::setTimeStep(float value)
{
	_newTimeStep = value;
}

void SPH::setGravity(const Vector & value)
{
	_newGravity = value;
}

void SPH::clear()
{
	_clear = true;
}

class DensityUpdateThread
	: public QThread
{
private:
	SPH * _sph;
	int _first;
	int _last;
	QSemaphore * _semaphore;
public:
	DensityUpdateThread(SPH * sph, int first, int last, QSemaphore * semaphore)
		: QThread()
		, _sph(sph)
		, _first(first)
		, _last(last)
		, _semaphore(semaphore)
	{
		/* does nothing */
	}
protected:
	void run()
	{
		DensityUpdater().updateDensityParallel(*_sph, _first, _last);
		_semaphore->release();
	}
};

class ForceUpdateThread
	: public QThread
{
private:
	SPH * _sph;
	int _first;
	int _last;
	QSemaphore * _semaphore;
public:
	ForceUpdateThread(SPH * sph, int first, int last, QSemaphore * semaphore)
		: QThread()
		, _sph(sph)
		, _first(first)
		, _last(last)
		, _semaphore(semaphore)
	{
		/* does nothing */
	}
protected:
	void run()
	{
		ForceUpdater().updateForcesParallel(*_sph, _first, _last);
		_semaphore->release();
	}
};

class PositionUpdateThread
	: public QThread
{
private:
	SPH * _sph;
	int _first;
	int _last;
	QSemaphore * _semaphore;
public:
	PositionUpdateThread(SPH * sph, int first, int last, QSemaphore * semaphore)
		: QThread()
		, _sph(sph)
		, _first(first)
		, _last(last)
		, _semaphore(semaphore)
	{
		/* does nothing */
	}
protected:
	void run()
	{
		PositionUpdater().updatePositionParallel(*_sph, _first, _last);
		_semaphore->release();
	}
};

void SPH::step()
{
	
	updateMembers();
	
	const int ATOM_COUNT = atomVoxelizer.count();
	const int THREAD_COUNT = QThread::idealThreadCount();
	
	if(THREAD_COUNT > 1 && ATOM_COUNT >= 4)
	{
		QSemaphore semaphore;
		QVector<ForceUpdateThread *> threadForce(THREAD_COUNT);
		QVector<DensityUpdateThread *> threadDensity(THREAD_COUNT);
		QVector<PositionUpdateThread *> threadPosition(THREAD_COUNT);
		
		const int ATOM_PER_THREAD = ATOM_COUNT / THREAD_COUNT;
		
		// Initialize Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			int first = i * ATOM_PER_THREAD;
			int last;
			
			// Last Thread take the remaining atoms
			if(i == THREAD_COUNT - 1)
			{
				last = ATOM_COUNT - 1;
			}
			// Others Thread take the same amount of atom
			else
			{
				last = first + ATOM_PER_THREAD;
			}
			
			threadForce[i] = new ForceUpdateThread(this, first, last, &semaphore);
			threadDensity[i] = new DensityUpdateThread(this, first, last, &semaphore);
			threadPosition[i] = new PositionUpdateThread(this, first, last, &semaphore);
		}
		
		// Start Density Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			threadDensity[i]->start();
		}
		
		// Barrier for Density Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			semaphore.acquire();
		}
		
		
		// Start Force Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			threadForce[i]->start();
		}
		
		// Barrier for Force Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			semaphore.acquire();
		}
		
		// Start Position Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			threadPosition[i]->start();
		}
		
		// Barrier for Position Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			semaphore.acquire();
		}
		
		// Delete Threads
		for(int i = 0; i < THREAD_COUNT; ++i)
		{
			threadForce[i]->wait();
			threadDensity[i]->wait();
			threadPosition[i]->wait();
			delete threadForce[i];
			delete threadDensity[i];
			delete threadPosition[i];
		}
		
	}
	else
	{
		DensityUpdater().updateDensity(*this);
		ForceUpdater().updateForces(*this);
		PositionUpdater().updatePosition(*this);
	}
	
	
	atomVoxelizer.rehash();
}

void SPH::updateMembers()
{
	_lastTimeStep = _currentTimeStep;
	_currentTimeStep = _newTimeStep;
	_currentGravity = _newGravity;
	atomVoxelizer.setSmoothingDistance(_newSmoothingDistance);
	if(_clear)
	{
		_clear = false;
		atomVoxelizer.clear();
	}
}

QDataStream & operator<< (QDataStream & stream, const ParaFluid::SPH & sph)
{
	sph.serialize(stream);
	return stream;
}

QDataStream & operator>> (QDataStream & stream, ParaFluid::SPH & sph)
{
	sph.unserialize(stream);
	return stream;
}
